---
title: Merge documents
summary: Converts non-PDF inputs, merges all input documents into one, and optionally validates PDF/A conformance.
status: draft
owner: Not determined from source
last_reviewed: 2026-09-17
library: PdfAPI
reference_type: embedded
operation_id: OP-MERGE-DOCUMENTS
trigger: Client submits a POST v1/merge request with a MergeRequest body
---

# OP-MERGE-DOCUMENTS: Merge documents

Merges a set of input documents into one output document, converting any non-PDF inputs to PDF first, and optionally producing PDF/A output.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-MERGE-DOCUMENTS |
| **Trigger** | Client submits `POST v1/merge` with a `MergeRequest` body |
| **Entry endpoint** | [`POST v1/merge`](../endpoints/merge-controller.md#post-v1%2Fmerge) |
| **Owning component** | `MergeController.Merge(MergeRequest)` → `IPdfService.Merge(request)` |
| **Inputs** | `MergeRequest` body (`Documents` array, `Options`) |
| **Outputs** | `PdfResult` (`Status`, `Message`, `Document`, `TrackingId`); a `PDFTracking` row created/updated |
| **Failure response** | A null body fails as a framework error; other caught exceptions are logged and returned as `Ok(new PdfResult(message))` — HTTP `200` |

## Steps

1. `Enforce.ArgumentNotNull(request, "request")` runs **before** the `try` block ([BR-PDFAPI-001](../business-rules.md#br-pdfapi-001-null-merge-request-bypasses-the-error-envelope)).
   - On failure (`request == null`): the exception escapes to the framework — not caught by the action, so no `PdfResult` envelope is produced; the framework produces exception detail (`IncludeErrorDetailPolicy.Always`) before `ApiResponseHandler` runs, and the final client-visible envelope is unknown** (see [Endpoints: Error response shape](../endpoints.md#error-response-shape)).
2. `request.Options ??= new PdfOptions()` substitutes a default (non-PDF/A) options object when the caller omitted `Options` ([BR-PDFAPI-002](../business-rules.md#br-pdfapi-002-default-merge-options-substitution)).
3. The remaining call is wrapped in `try`/`catch` ([BR-PDFAPI-017](../business-rules.md#br-pdfapi-017-processing-failures-return-200-with-a-pdfresult)).
   - On any exception: `_logService.LogException("pdfapi", "Merge", "<detail>", ex)`, then `Ok(new PdfResult(ex.Message, request: request))` — HTTP `200`.
4. If tracking is enabled (default, or forced on by `SendHeatEmailOnFailure`, [BR-PDFAPI-010](../business-rules.md#br-pdfapi-010-tracking-forced-on-with-heat-email)), `PdfTrackingService.CreateTrackingRecord` inserts a `PDFTracking` row.
5. Each non-PDF input document is converted to PDF first; if any pre-conversion fails, a `PdfException` is thrown and the **entire merge is abandoned** — no partial output is produced ([BR-PDFAPI-006](../business-rules.md#br-pdfapi-006-merge-aborts-entirely-on-pre-conversion-failure)).
6. `PdfMerger.Merge` (`Syncfusion.Pdf.PdfDocument.Merge`) combines the converted/validated documents into one output.

   This merge logic is implemented in `PdfApi.SyncFusionCore` (`PdfMerger.Merge`), documented in full at [Merge document ranges into one PDF](../../../libraries/pdfapi-syncfusioncore/operations/merge-document-ranges-into-one-pdf.md) (OP-SFC-012); its behavior is summarized here only as observed on this request path.

   > **Origin:** The document-merge behavior described in this step is implemented in
   > `PdfApi.SyncFusionCore`, not in `PdfAPI`. See the linked operation page for the full
   > delegated logic.
7. If `Options.IsPdfa` and the merged output fails `ValidatePdfA`, `OutputFormat` is **silently downgraded** to `PdfFormat.Pdf` rather than failing the request ([BR-PDFAPI-007](../business-rules.md#br-pdfapi-007-pdf-a-output-silently-downgraded)).
8. On the ActivePDF processor: a PDF/A production failure is retried as plain PDF, and configured `pdfApi.ErrorsToRetry` error codes trigger a whole-operation retry ([BR-PDFAPI-020](../business-rules.md#br-pdfapi-020-activepdf-retry-behavior)).
9. `FinalizeResult` stamps `TrackingId`, `PdfProcessorType`, `FilePath`, then persists the terminal tracking status via `PdfTrackingService.UpdateTrackingRecord`.
10. If the result failed and `SendHeatEmailOnFailure` is set, an incident email is sent ([BR-PDFAPI-015](../business-rules.md#br-pdfapi-015-incident-email-on-failure)).
11. The controller returns `Ok(result)` — HTTP `200` regardless of whether the merge succeeded or failed.

## Process flow

The following diagram shows the pre-conversion, merge, PDF/A-downgrade, and incident-email sequence for a merge request.

```mermaid
flowchart TD
    A["POST v1/merge"] --> B{"request null?"}
    B -->|Yes| C["Framework 500 error, no PdfResult"]
    B -->|No| D["Substitute default Options if null"]
    D --> E["Create tracking record"]
    E --> F["Convert non-PDF inputs to PDF"]
    F -->|Any pre-conversion fails| G["PdfException: merge abandoned"]
    F -->|All succeed| H["PdfMerger.Merge combines documents"]
    H --> I{"IsPdfa requested and<br>validation fails?"}
    I -->|Yes| J["Silently downgrade to Pdf"]
    I -->|No| K["Keep requested format"]
    J --> L["FinalizeResult: persist terminal status"]
    K --> L
    L --> M{"Result failed and<br>SendHeatEmailOnFailure?"}
    M -->|Yes| N["Send incident email"]
    M -->|No| O["Ok result"]
    N --> O
```

## Transaction and side effects

| Aspect | Finding |
| --- | --- |
| Unit of work | `IUnitOfWorkFactory("oracle")`, `InstancePerRequest` |
| Commit point | `PdfTrackingService.CreateTrackingRecord` and `UpdateTrackingRecord`, each bracketed by `_uow.Begin()`/`_uow.Commit()` |
| Records written | `PDFTracking` ([data model](../data-model.md#pdftracking-properties-observed-in-use)) |
| Downstream calls | Outbound SMTP email on failure when `SendHeatEmailOnFailure` is set |
| Repeat-call behavior | Each call creates a new tracking record; not idempotent |

## Logging

| Surface | Written content |
| --- | --- |
| `ILogService.LogException` | `"pdfapi"`, `"Merge"`, and exception detail, on any caught failure |

## Error handling

- Null request body: escapes to the framework before the `try` block, producing framework exception detail before `ApiResponseHandler` runs; the final client-visible envelope is unknown**, and no `PdfResult` is produced ([BR-PDFAPI-001](../business-rules.md#br-pdfapi-001-null-merge-request-bypasses-the-error-envelope)).
- Pre-conversion failure on any input: `PdfException`, caught, logged, entire merge abandoned, `200` with a failure `PdfResult` ([BR-PDFAPI-006](../business-rules.md#br-pdfapi-006-merge-aborts-entirely-on-pre-conversion-failure)).
- PDF/A requested but not achieved: not an error — silent downgrade ([BR-PDFAPI-007](../business-rules.md#br-pdfapi-007-pdf-a-output-silently-downgraded)).

## Related documentation
- [API index](../index.md)
- [Overview](../overview.md)
- [Architecture](../architecture.md)
- [Endpoints](../endpoints.md)
- [Authentication and authorization](../authentication-authorization.md)
- [Dependencies](../dependencies.md)
- [Configuration](../configuration.md)
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Data model](../data-model.md)
- [Operational runbook](../operational-runbook.md)
- [Tech debt](../tech-debt.md)

> **Note:** Statements marked with `**` could not be confirmed; treat as unknown until verified.

## Sources
- PdfAPI\Controllers\v1\MergeController.cs
- PdfAPI\Services\PdfService.cs
- PdfAPI\Services\PdfTrackingService.cs
- PdfAPI\Services\Syncfusion\SyncfusionPdfService.cs
- PdfAPI\Services\ActivePdf\ActivePdfService.cs
- PdfAPI.Core\Request\MergeRequest.cs
